fix(a11y): Accessibility improvements for WCAG 2.1 Level A compliance#37211
fix(a11y): Accessibility improvements for WCAG 2.1 Level A compliance#37211Aitema-gmbh wants to merge 4 commits intoapache:masterfrom
Conversation
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
This PR introduces comprehensive accessibility improvements to meet WCAG 2.1 Level A compliance standards. ## Changes ### New Accessibility Components - **SkipLink**: Skip-to-content navigation for keyboard users - **StatusAnnouncer**: ARIA live region for dynamic content updates ### Enhanced Semantic Structure - **SliceHeader**: Added semantic h2 headings for chart titles (WCAG 1.3.1) - **ActionButtons**: Improved focus management and keyboard navigation ### Focus Management - Programmatic focus management in SkipLink with fallback scroll - Proper tabindex handling for interactive elements ## WCAG Criteria Addressed - 1.3.1 Info and Relationships (Level A) - 2.1.1 Keyboard (Level A) - 2.4.1 Bypass Blocks (Level A) - 4.1.2 Name, Role, Value (Level A)
4eff25c to
dfcf5ea
Compare
✅ Deploy Preview for superset-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
CodeAnt AI finished reviewing your PR. |
There was a problem hiding this comment.
Code Review Agent Run #bd4964
Actionable Suggestions - 2
-
superset-frontend/src/components/Accessibility/SkipLink.tsx - 1
- Skip link breaks target focusability · Line 72-75
-
superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/index.tsx - 1
- Incorrect clear button enable logic · Line 115-124
Additional Suggestions - 1
-
superset-frontend/src/dashboard/components/SliceHeader/index.tsx - 1
-
Typo in error message · Line 57-57The error message string contains a typo: 'One ore more' should be 'One or more'. This affects the user-facing text when annotation layers fail to load.
Code suggestion
diff --git a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx index abc123..def456 100644 --- a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx @@ -57,1 +57,1 @@ -const annotationsError = t('One ore more annotation layers failed loading.'); +const annotationsError = t('One or more annotation layers failed loading.');
-
Review Details
-
Files reviewed - 5 · Commit Range:
4eff25c..4eff25c- superset-frontend/src/components/Accessibility/SkipLink.tsx
- superset-frontend/src/components/Accessibility/StatusAnnouncer.tsx
- superset-frontend/src/components/Accessibility/index.tsx
- superset-frontend/src/dashboard/components/SliceHeader/index.tsx
- superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/index.tsx
-
Files skipped - 0
-
Tools
- Whispers (Secret Scanner) - ✔︎ Successful
- Detect-secrets (Secret Scanner) - ✔︎ Successful
Bito Usage Guide
Commands
Type the following command in the pull request comment and save the comment.
-
/review- Manually triggers a full AI review. -
/pause- Pauses automatic reviews on this pull request. -
/resume- Resumes automatic reviews. -
/resolve- Marks all Bito-posted review comments as resolved. -
/abort- Cancels all in-progress reviews.
Refer to the documentation for additional commands.
Configuration
This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.
Documentation & Help
| el.focus(); | ||
| if (!hadTabIndex) { | ||
| el.removeAttribute('tabindex'); | ||
| } |
There was a problem hiding this comment.
The handleClick function adds tabindex='-1' to non-focusable elements to enable programmatic focus, but then removes it afterward, making the target unfocusable again. This breaks skip link functionality for elements that weren't originally focusable. The tabindex should be left in place to maintain focusability.
Code suggestion
Check the AI-generated fix before applying
| el.focus(); | |
| if (!hadTabIndex) { | |
| el.removeAttribute('tabindex'); | |
| } | |
| el.focus(); |
Code Review Run #bd4964
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
| const isClearAllEnabled = useMemo( | ||
| () => | ||
| Object.values(dataMaskApplied).some( | ||
| filter => | ||
| isDefined(dataMaskSelected[filter.id]?.filterState?.value) || | ||
| (!dataMaskSelected[filter.id] && | ||
| isDefined(filter.filterState?.value)), | ||
| ), | ||
| [dataMaskApplied, dataMaskSelected], | ||
| ); |
There was a problem hiding this comment.
The new logic for isClearAllEnabled only enables the clear all button for filters already in dataMaskApplied, missing cases where filters in dataMaskSelected (not yet applied) have values that should be clearable. This changes observable behavior, potentially disabling the button when users expect it to be enabled for pending selections.
Code suggestion
Check the AI-generated fix before applying
| const isClearAllEnabled = useMemo( | |
| () => | |
| Object.values(dataMaskApplied).some( | |
| filter => | |
| isDefined(dataMaskSelected[filter.id]?.filterState?.value) || | |
| (!dataMaskSelected[filter.id] && | |
| isDefined(filter.filterState?.value)), | |
| ), | |
| [dataMaskApplied, dataMaskSelected], | |
| ); | |
| const isClearAllEnabled = useMemo(() => { | |
| const hasSelectedValues = Object.values(dataMaskSelected).some( | |
| mask => isDefined(mask.filterState?.value) | |
| ); | |
| const hasAppliedValues = Object.values(dataMaskApplied).some( | |
| mask => isDefined(mask.filterState?.value) | |
| ); | |
| return hasSelectedValues || hasAppliedValues; | |
| }, [dataMaskSelected, dataMaskApplied]); |
Code Review Run #bd4964
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
- SkipLink: Keep tabindex=-1 after focus to maintain keyboard focusability - ActionButtons: Fix isClearAllEnabled to check both pending (dataMaskSelected) and applied (dataMaskApplied) filter values separately
There was a problem hiding this comment.
Code Review Agent Run #d18a72
Actionable Suggestions - 1
-
superset-frontend/src/dashboard/components/SliceHeader/index.tsx - 1
- Typo in error message · Line 57-57
Additional Suggestions - 2
-
superset-frontend/src/dashboard/components/SliceHeader/index.tsx - 2
-
Import path violates modernization rules · Line 31-31The import path for EditableTitle should use '@superset-ui/core' instead of the local 'src/components/EditableTitle', following the ongoing frontend modernization to use @superset-ui/core for UI components.
Code suggestion
@@ -31,1 +31,1 @@ - import EditableTitle from 'src/components/EditableTitle'; + import { EditableTitle } from '@superset-ui/core';
-
Avoid 'any' type for better type safety · Line 183-183The use of 'any' type for crossFilterValue violates TypeScript best practices and the codebase's modernization toward full type safety. Consider defining a proper type for the filter value.
-
Review Details
-
Files reviewed - 5 · Commit Range:
dfcf5ea..dfcf5ea- superset-frontend/src/components/Accessibility/SkipLink.tsx
- superset-frontend/src/components/Accessibility/StatusAnnouncer.tsx
- superset-frontend/src/components/Accessibility/index.tsx
- superset-frontend/src/dashboard/components/SliceHeader/index.tsx
- superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/index.tsx
-
Files skipped - 0
-
Tools
- Whispers (Secret Scanner) - ✔︎ Successful
- Detect-secrets (Secret Scanner) - ✔︎ Successful
Bito Usage Guide
Commands
Type the following command in the pull request comment and save the comment.
-
/review- Manually triggers a full AI review. -
/pause- Pauses automatic reviews on this pull request. -
/resume- Resumes automatic reviews. -
/resolve- Marks all Bito-posted review comments as resolved. -
/abort- Cancels all in-progress reviews.
Refer to the documentation for additional commands.
Configuration
This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.
Documentation & Help
|
|
||
| const annotationsLoading = t('Annotation layers are still loading.'); | ||
| const annotationsError = t('One or more annotation layers failed loading.'); | ||
| const annotationsError = t('One ore more annotation layers failed loading.'); |
There was a problem hiding this comment.
The string 'One ore more annotation layers failed loading.' contains a typo; it should be 'One or more'. This affects user-facing error messages when annotation layers fail.
Code suggestion
Check the AI-generated fix before applying
| const annotationsError = t('One ore more annotation layers failed loading.'); | |
| const annotationsError = t('One or more annotation layers failed loading.'); |
Code Review Run #d18a72
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
Code Review Agent Run #c4104fActionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
|
/resolve ✅ All Bito Review Issues Have Been AddressedThe review comments reference the original commit Fixed in commit
|
|
Hi team! 👋 This PR is ready for review. All bot feedback has been addressed:
The latest Bito review shows 0 actionable suggestions. Could a maintainer please review when you have time? This adds important WCAG 2.1 Level A accessibility improvements. cc @geido @kgabryje @michael-s-molina Thanks! 🙏 |
…mponents - Add 34 comprehensive tests for SkipLink component - Add 46 comprehensive tests for StatusAnnouncer component - Add 5 Storybook stories for SkipLink with interactive demos - Add 5 Storybook stories for StatusAnnouncer with real-world examples
|
CodeAnt AI is running Incremental review Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
| <main | ||
| id="main-content" | ||
| tabIndex={-1} | ||
| style={{ padding: 40, outline: 'none' }} |
There was a problem hiding this comment.
Suggestion: The <main> element explicitly sets outline: 'none', which removes the browser focus indicator and contradicts the accessibility checklist (focus indicator should be visible). Removing the outline: 'none' (or replacing it with a visible focus style) ensures keyboard users can see focus when the skip link targets the main area. [possible bug]
Severity Level: Critical 🚨
- ❌ Keyboard users cannot see focus on main content.
- ⚠️ Accessibility test story fails visible focus check.
- ⚠️ WCAG keyboard navigation validation impacted.| style={{ padding: 40, outline: 'none' }} | |
| style={{ padding: 40 }} |
Steps of Reproduction ✅
1. Open the FullPageDemo story in
superset-frontend/src/components/Accessibility/SkipLink.stories.tsx (FullPageDemo defined
lines 79-117). The main target element is declared at lines 101-105 with tabIndex={-1} and
style outline: 'none'.
2. Start Storybook and navigate to the FullPageDemo story. Press Tab until the skip link
appears (as described in the story) and activate it (click or press Enter).
3. The SkipLink component should programmatically move focus to the target element with
id="main-content" (the story documents this expectation). Because the main element has
style outline: 'none' (lines 101-105), the browser's visible focus indicator will be
suppressed.
4. Observe that after activation the target receives focus but there is no visible focus
ring, failing the story's own checklist (visible focus indicator) and making keyboard
users unable to perceive focus location.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** superset-frontend/src/components/Accessibility/SkipLink.stories.tsx
**Line:** 104:104
**Comment:**
*Possible Bug: The `<main>` element explicitly sets `outline: 'none'`, which removes the browser focus indicator and contradicts the accessibility checklist (focus indicator should be visible). Removing the `outline: 'none'` (or replacing it with a visible focus style) ensures keyboard users can see focus when the skip link targets the main area.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| const preventDefaultSpy = jest.spyOn(clickEvent, 'preventDefault'); | ||
|
|
||
| link.dispatchEvent(clickEvent); | ||
| expect(preventDefaultSpy).toHaveBeenCalled(); |
There was a problem hiding this comment.
Suggestion: Spying on preventDefault on a native MouseEvent instance can fail in jsdom because the instance method may not be spyable; replace spying on the instance with spying on Event.prototype.preventDefault and restore the spy after the assertion to avoid test errors and global side effects. [possible bug]
Severity Level: Critical 🚨
- ❌ SkipLink test can fail during CI runs.
- ⚠️ Local developer test flakiness while running tests.| const preventDefaultSpy = jest.spyOn(clickEvent, 'preventDefault'); | |
| link.dispatchEvent(clickEvent); | |
| expect(preventDefaultSpy).toHaveBeenCalled(); | |
| const preventDefaultSpy = jest.spyOn(Event.prototype, 'preventDefault'); | |
| link.dispatchEvent(clickEvent); | |
| expect(preventDefaultSpy).toHaveBeenCalled(); | |
| preventDefaultSpy.mockRestore(); |
Steps of Reproduction ✅
1. Run the SkipLink test file with the specific test:
yarn test superset-frontend/src/components/Accessibility/SkipLink.test.tsx -t "prevents
default anchor behavior".
2. Test executes the block in this file at
superset-frontend/src/components/Accessibility/SkipLink.test.tsx lines 157-161
(inside test 'prevents default anchor behavior' which creates a MouseEvent and calls
jest.spyOn on the instance).
3. In a jsdom environment (used by Jest), the instance method may not be spyable;
jest.spyOn(clickEvent, 'preventDefault')
can throw or not attach correctly, causing the test to error or give a false negative
at the call on line 158.
4. Observed outcome: test fails with a spy-related error (cannot spy property / not a
function) or flaky assertion.
The proposed change (spy on Event.prototype and restore) avoids spying on a possibly
non-configurable instance method.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** superset-frontend/src/components/Accessibility/SkipLink.test.tsx
**Line:** 158:161
**Comment:**
*Possible Bug: Spying on `preventDefault` on a native MouseEvent instance can fail in jsdom because the instance method may not be spyable; replace spying on the instance with spying on `Event.prototype.preventDefault` and restore the spy after the assertion to avoid test errors and global side effects.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| // Note: We intentionally keep the tabindex to ensure the element remains focusable | ||
| // for subsequent keyboard navigation (fixes skip link accessibility) | ||
| if (!el.hasAttribute('tabindex')) { | ||
| el.setAttribute('tabindex', '-1'); | ||
| } | ||
| el.focus(); |
There was a problem hiding this comment.
Suggestion: Logic bug: the code sets a permanent tabindex="-1" (when absent) and leaves it on the target element — tabindex="-1" removes the element from the keyboard tab order, preventing users from reaching main content via Tab; instead temporarily add tabindex="-1" only to programmatically focus and then remove it (after focus) so normal keyboard navigation is preserved. [logic error]
Severity Level: Critical 🚨
- ❌ Skip link keyboard navigation fails reaching main content.
- ⚠️ Keyboard users cannot access targeted content via Tab.
- ⚠️ Breaks WCAG 2.4.1 bypass block compliance.| // Note: We intentionally keep the tabindex to ensure the element remains focusable | |
| // for subsequent keyboard navigation (fixes skip link accessibility) | |
| if (!el.hasAttribute('tabindex')) { | |
| el.setAttribute('tabindex', '-1'); | |
| } | |
| el.focus(); | |
| // Temporarily add tabindex so we can programmatically focus, then remove it to preserve tab order | |
| const _addedTabIndex = !el.hasAttribute('tabindex'); | |
| if (_addedTabIndex) { | |
| el.setAttribute('tabindex', '-1'); | |
| } | |
| el.focus(); | |
| if (_addedTabIndex) { | |
| // Remove the temporary tabindex on the next macrotask to ensure focus is retained | |
| window.setTimeout(() => { | |
| if (document.body.contains(el)) { | |
| el.removeAttribute('tabindex'); | |
| } | |
| }, 0); | |
| } |
Steps of Reproduction ✅
1. Ensure the SkipLink component is present on a page (component defined at
superset-frontend/src/components/Accessibility/SkipLink.tsx). The click handler starts at
the code around lines 64-76; the relevant block is lines 67-73.
2. Add a target element without an existing tabindex, e.g. <main
id="main-content">...</main>, and confirm no tabindex attribute is present.
3. Focus the SkipLink (Tab until it appears) and activate it (Enter or click). This calls
handleClick() in SkipLink (see lines 64-76), which executes the existing lines 67-73.
4. After activation, the code sets tabindex="-1" on the target and leaves it. Observe that
subsequent Tab presses do NOT reach the main content because tabindex="-1" removes it from
tab order — reproducing the accessibility regression.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** superset-frontend/src/components/Accessibility/SkipLink.tsx
**Line:** 68:73
**Comment:**
*Logic Error: Logic bug: the code sets a permanent `tabindex="-1"` (when absent) and leaves it on the target element — `tabindex="-1"` removes the element from the keyboard tab order, preventing users from reaching main content via Tab; instead temporarily add `tabindex="-1"` only to programmatically focus and then remove it (after focus) so normal keyboard navigation is preserved.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.|
CodeAnt AI Incremental review completed. |
Code Review Agent Run #a3d201Actionable Suggestions - 0Additional Suggestions - 1
Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
|
Could you please take a look at this PR when you have a moment? It adds WCAG 2.1 Level A accessibility improvements including:
The Bito bot suggestions have been addressed in subsequent commits. Would appreciate a review from the team! Thanks! 🙏 |
There was a problem hiding this comment.
Pull request overview
This pull request aims to introduce accessibility improvements to Apache Superset for WCAG 2.1 Level A compliance. While the new accessibility components (SkipLink and StatusAnnouncer) are well-implemented with comprehensive tests and documentation, the modifications to existing components contain multiple critical issues.
Changes:
- New accessibility components: SkipLink (bypass blocks) and StatusAnnouncer (ARIA live regions)
- Modified SliceHeader to use semantic
<h2>headings for chart titles - Updated ActionButtons with tooltip support and refactored styling
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
superset-frontend/src/components/Accessibility/index.tsx |
Exports new accessibility components |
superset-frontend/src/components/Accessibility/SkipLink.tsx |
Skip-to-content link component with one documentation issue |
superset-frontend/src/components/Accessibility/SkipLink.test.tsx |
Comprehensive test suite for SkipLink |
superset-frontend/src/components/Accessibility/SkipLink.stories.tsx |
Storybook examples for SkipLink |
superset-frontend/src/components/Accessibility/StatusAnnouncer.tsx |
ARIA live region provider for screen readers |
superset-frontend/src/components/Accessibility/StatusAnnouncer.test.tsx |
Comprehensive test suite for StatusAnnouncer |
superset-frontend/src/components/Accessibility/StatusAnnouncer.stories.tsx |
Storybook examples for StatusAnnouncer |
superset-frontend/src/dashboard/components/SliceHeader/index.tsx |
CRITICAL ISSUES: Multiple incorrect import paths, wrong EditableTitle API usage, removed features creating breaking changes |
superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/index.tsx |
CRITICAL ISSUES: Incorrect import paths, removed features, potential filter logic regression |
| import { useSelector } from 'react-redux'; | ||
| import SliceHeaderControls from 'src/dashboard/components/SliceHeaderControls'; | ||
| import { SliceHeaderControlsProps } from 'src/dashboard/components/SliceHeaderControls/types'; | ||
| import EditableTitle from 'src/components/EditableTitle'; |
There was a problem hiding this comment.
The import path for EditableTitle is incorrect. The component should be imported from '@superset-ui/core' or '@superset-ui/core/components' where it is exported (as shown in line 104 of packages/superset-ui-core/src/components/index.ts), not from 'src/components/EditableTitle' which doesn't exist. This will cause a module resolution error.
| import EditableTitle from 'src/components/EditableTitle'; | |
| import EditableTitle from '@superset-ui/core'; |
| import { useUiConfig } from 'src/components/UiConfigContext'; | ||
| import { isEmbedded } from 'src/dashboard/util/isEmbedded'; | ||
| import { Tooltip, EditableTitle, Icons } from '@superset-ui/core/components'; | ||
| import { Tooltip } from 'src/components/Tooltip'; |
There was a problem hiding this comment.
The import path for Tooltip is incorrect. The component should be imported from '@superset-ui/core' or '@superset-ui/core/components' where it is exported, not from 'src/components/Tooltip' which doesn't exist. This will cause a module resolution error.
|
Closing this PR in favor of #38342, which:
The scope is now focused on the new accessibility components (SkipLink, StatusAnnouncer) and the minimal SliceHeader semantic heading change. |
Summary
This PR introduces comprehensive accessibility improvements to Apache Superset to meet WCAG 2.1 Level A compliance standards.
Changes
New Accessibility Components:
SkipLink: Skip-to-content navigation for keyboard users with programmatic focus managementStatusAnnouncer: ARIA live region for announcing dynamic content updates to screen readersEnhanced Semantic Structure:
SliceHeader: Added semantic<h2>headings for chart titles (WCAG 1.3.1 - Info and Relationships)ActionButtons: Improved focus management and keyboard navigation for native filtersWCAG Criteria Addressed
Technical Notes
Testing Instructions
Checklist